(function() {

    var
    $u = juliet.using({
        array : 'juliet.core.array-0.1',
        object : 'juliet.core.object-0.1',
        validationEvent : 'juliet.validation.validationEvent-0.2',
        validationResult : 'juliet.validation.validationResult-0.2',
        Observer : 'juliet.core.Observer-0.1',
        ValidatorsChain : 'juliet.validation.ValidatorsChain-0.2'
    }),
    ve = $u('validationEvent'),
    vr = $u('validationResult'),
    $ = juliet.jQuery;

    var inProgressValidators = {

        uidsHash : { },

        count : 0,

        add : function(uid) {
            if (this.has(uid)) return;

            this.uidsHash[uid] = true;
            ++this.count;
        },

        remove : function(uid) {
            if (!this.has(uid)) return;

            delete this.uidsHash[uid];
            --this.count;
        },

        has : function(uid) {
            return (this.uidsHash[uid] === true);
        },

        isValidationInProgress : function() {
            return (this.count != 0);
        }

    };

    var ValidationManagerClass = function() {

        this.observer = juliet.createObject($u('Observer'));

        this.chains = { };

        this.prepareChainData = function(chainData) {
            chainData.manager = this;
            return chainData;
        };

        this.addChain = function(chainData) {
            var chain = juliet.createObject($u('ValidatorsChain'), this.prepareChainData(chainData));
            this.chains[chain.uid] = chain;

            this.subscribeOnChainEvents(chain);
        };

        this.addChains = function(chainsData) {
            $u('array').each(chainsData, function(item) {
                this.addChain(item);
            }, this);
        };

        this.chainOnShowErrorHandler = function(chain) {
            this.setError(chain, chain.validators.failed.object.getError());
        };

        this.chainOnHideErrorHandler = function(chain) {
            this.setError(chain, '');
        };

        this.chainOnValidatedHandler = function(chain) {
            if (chain.getResult() == vr.IN_PROGRESS) {
                inProgressValidators.add(chain.uid);
            } else {
                inProgressValidators.remove(chain.uid);
            }

            this.observer.notify('chain-validated', chain);
        };

        this.subscribeOnChainEvents = function(chain) {
            chain.observer.subscribe('show-error', this.chainOnShowErrorHandler, this);
            chain.observer.subscribe('hide-error', this.chainOnHideErrorHandler, this);
            chain.observer.subscribe('validated', this.chainOnValidatedHandler, this);
        };

        this.validate = function() {
            $u('object').each(this.chains, function(item) {
                item.validate({ event : ve.MANAGER });
            });
        };

        this.isValidationInProgress = function() {
            return inProgressValidators.isValidationInProgress();
        };

        this.getResult = function() {
            if (this.isValidationInProgress()) {
                return vr.IN_PROGRESS;
            }

            var result = 0;

            $u('object').each(this.chains, function(item) {
                result |= item.getResult();
            });

            return vr.simplify(result);
        };

        this.setError = function(chain, error) {
            var errorElement;

            if (chain.options.errorSelector) {
                errorElement = $(chain.options.errorSelector);
            } else {
                errorElement = $(chain.inputHelper.selector).next('.information');
            }

            errorElement.html('<span class="error">' + error + '</span>');
        };

        this.initialize = function(chainsData) {
            this.addChains(chainsData);
        };

    };

    juliet.registerModule('juliet.validation', 'ValidationManager-0.2', ValidationManagerClass);

})();
